package com.ccl.iot.object;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import com.ccl.iot.publics.ClassManager;
import com.ccl.iot.publics.Debugger;
import com.ccl.iot.privates.ListMap;
import com.ccl.iot.IOTInfoMonitor.IOTInfoListener;
import com.ccl.iot.IOTInfoSynchro;

public class IOTSyncObjectCollector {
	public interface IOTObjectListener{
		public void onNewObject(Object aObject);
	}
	
	/*
	 * 	Non-static functions
	 */
	private Object iSyncLock = new Object();
	
	private IOTInfoSynchro iSynchro = null;
	
	private String iPrefix[] = null;
	private Class<?> iObjectClass[] = null;
	private IOTObjectListener iObjectListener = null;
	
	private ListMap<String, IOTSyncObjectInfo> iInfos = null;
	private ListMap<String, IOTAbsSyncObject> iObjects = null;
	
	public IOTSyncObjectCollector(IOTInfoSynchro aInfoSynchro, String aPrefix[], Class<?> aObjectClass[], IOTObjectListener aListener) {
		iSynchro = aInfoSynchro;
		
		if (aPrefix != null && aObjectClass != null && aPrefix.length == aObjectClass.length){
			iPrefix = aPrefix;
			iObjectClass = aObjectClass;
		}
		
		iObjectListener = aListener;
		
		if (iSynchro != null){
			iSynchro.addInfoListener(iInfoListener);
			
			if (iPrefix != null){
				ArrayList<IOTInfoWithValue> tInfos = iSynchro.getAllInfo();
				
				if (tInfos != null){
					for (int i = 0, tCount = tInfos.size(); i < tCount; i++){
						iInfoListener.onNewInfo(tInfos.get(i));
					}
				}
			}
		}
	}
	
	public ArrayList<IOTAbsSyncObject> getList(Class<?> aClass){
		if (iObjects != null){
			if (aClass == null){
				synchronized(iSyncLock){
					return iObjects.getValues();
				}
			}else{
				synchronized(iSyncLock){
					int tCount = iObjects.size();
					
					if (tCount > 0){
						ArrayList<IOTAbsSyncObject> tList = new ArrayList<IOTAbsSyncObject>();
						
						for (int i = 0; i < tCount; i++){
							IOTAbsSyncObject tObject = iObjects.getValueAt(i);
							
							if (tObject != null && tObject.getClass() == aClass){
								if (tList == null){
									tList = new ArrayList<IOTAbsSyncObject>();
								}
								
								tList.add(tObject);
							}
						}
						
						return tList;
					}
				}
			}
		}
		
		return null;
	}
	
	public Object findObject(String aID){
		if (iObjects != null){
			synchronized(iSyncLock){
				return iObjects.getValue(aID);
			}
		}
		
		return null;
	}
	
	private IOTInfoHeader decodeHeader(IOTInfoWithValue aInfo){
		if (aInfo != null && iPrefix != null){
			String tName = aInfo.getName();
			
			if (tName != null){
				for (int i = 0; i < iPrefix.length; i++){
					if (tName.startsWith(iPrefix[i])){
						return IOTInfoHeader.Decode(iPrefix[i], tName);
					}
				}
			}
		}
		
		return null;
	}
	
	private boolean buildObject(IOTSyncObjectInfo aInfo){
		if (aInfo != null && aInfo.isValid()){
			Class<?> tClass = null;
			IOTInfoHeader tHeader = aInfo.getHeader();
			
			if (iObjects != null){
				synchronized(iSyncLock){
					if (iObjects.containsKey(tHeader.getID())){
						return false;
					}
				}
			}
			
			for (int i = 0; i < iPrefix.length; i++){
				if (iPrefix[i].equals(tHeader.getPrefix())){
					tClass = iObjectClass[i];
					break;
				}
			}
			
			if (tClass != null){
				Constructor<?> tConstructor;
				
				if ((tConstructor = ClassManager.FindConstructor(tClass, aInfo, this)) != null){
					try {
						Object tObject = tConstructor.newInstance(aInfo, this);
						
						if (tObject != null && tObject instanceof IOTAbsSyncObject){
							if (iObjects == null){
								iObjects = new ListMap<String, IOTAbsSyncObject>();
							}
							
							synchronized(iSyncLock){
								iObjects.add(tHeader.getID(), (IOTAbsSyncObject) tObject);
							}
							
							if (iObjectListener != null){
								iObjectListener.onNewObject(tObject);
							}
							
							return true;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
		
		return false;
	}
	
	private void link(IOTSyncObjectInfo aInfo){
		if (aInfo != null){
			if (buildObject(aInfo)){
				if (iInfos != null){
					ArrayList<IOTSyncObjectInfo> tAccepted = null;
					
					synchronized(iSyncLock){
						for (int i = 0, tCount = iInfos.size(); i < tCount; i++){
							IOTSyncObjectInfo tSubInfo = iInfos.getValueAt(i);
							
							if (tSubInfo != null && tSubInfo != aInfo){
								if (tSubInfo.input(aInfo)){
									if (tAccepted == null){
										tAccepted = new ArrayList<IOTSyncObjectInfo>();
									}
									
									tAccepted.add(tSubInfo);
								}
							}
						}
					}
					
					if (tAccepted != null){
						for (int i = 0, tCount = tAccepted.size(); i < tCount; i++){
							link(tAccepted.get(i));
						}
					}
				}
			}else{
				if (aInfo.isValid(false)){
					if (iInfos != null){
						boolean tAccepted = false;
						
						synchronized(iSyncLock){
							for (int i = 0, tCount = iInfos.size(); i < tCount; i++){
								IOTSyncObjectInfo tSubInfo = iInfos.getValueAt(i);
								
								if (tSubInfo != null && tSubInfo != aInfo){
									if (aInfo.input(tSubInfo)){
										tAccepted = true;
									}
								}
							}
						}
						
						if (tAccepted){
							link(aInfo);
						}
					}
				}
			}
		}
	}
	
	private IOTSyncObjectInfo getObjectInfo(IOTInfoWithValue aInfo){
		if (iInfos != null){
			IOTInfoHeader tHeader = decodeHeader(aInfo);
			
			if (tHeader != null){
				synchronized(iSyncLock){
					return iInfos.getValue(tHeader.getID());
				}
			}
		}
		
		return null;
	}
	
	private IOTInfoListener iInfoListener = new IOTInfoListener(){
		@Override
		public void onNewInfo(IOTInfoWithValue aInfo) {
			IOTInfoHeader tHeader = decodeHeader(aInfo);
			
			if (tHeader != null){
				IOTSyncObjectInfo tInfo = null;
				
				if (iInfos == null){
					iInfos = new ListMap<String, IOTSyncObjectInfo>();
				}else{
					synchronized(iSyncLock){
						tInfo = iInfos.getValue(tHeader.getID());
					}
				}
				
				if (tInfo == null){
					tInfo = new IOTSyncObjectInfo(tHeader);
					synchronized(iSyncLock){
						iInfos.add(tHeader.getID(), tInfo);
					}
				}

				if (tInfo.input(tHeader.getName(), aInfo.getValue())){
					link(tInfo);
				}
			}
		}

		@Override
		public void onInfoChanged(IOTInfoWithValue aInfo) {
			IOTSyncObjectInfo tInfo = getObjectInfo(aInfo);
			
			if (tInfo != null){
				
			}
		}

		@Override
		public void onInfoRemoved(IOTInfoWithValue aInfo) {
		}
	};
	
	public boolean save(String aSyncID, IOTSyncObjectInfo aInfo, HashMap<String, String> aInfos){
		if (aSyncID != null && aInfo != null && aInfos != null && iSynchro != null){
			Iterator<Entry<String, String>> tIterator = aInfos.entrySet().iterator();
			
			synchronized(iSyncLock){
				if (iInfos == null){
					iInfos = new ListMap<String, IOTSyncObjectInfo>();
				}
				
				iInfos.put(aSyncID, aInfo);
			}
			
			while(tIterator.hasNext()){
				Entry<String, String> tEntry = tIterator.next();
				
				if (tEntry != null){
					iSynchro.setInfoValue(tEntry.getKey(), tEntry.getValue());
				}
			}
			
			return true;
		}
		
		return false;
	}
	
	public boolean save(IOTAbsSyncObject aObject, boolean aIncludeNest){
		if (aObject != null && iSynchro != null){
			IOTSyncObjectInfo tInfo = new IOTSyncObjectInfo(aObject, aIncludeNest);
			HashMap<String, String> tInfos = tInfo.output(aIncludeNest);
			
			return save(aObject.getSyncID(), tInfo, tInfos);
		}
		
		return false;
	}
	
	//TODO:modify function
	public boolean modify(IOTAbsSyncObject aObject){
		if (remove(aObject)){
			return add(aObject);
		}
		
		if (aObject != null && iObjects != null && iInfos != null && iSynchro != null){
			String tKey;
			
			synchronized(iSyncLock){
				tKey = iObjects.getKey(aObject);
			}
			
			if (tKey != null){
				IOTSyncObjectInfo tOldInfo;
				
				synchronized(iSyncLock){
					tOldInfo = iInfos.getValue(tKey);
				}
				
				if (tOldInfo != null){/////////////
					IOTSyncObjectInfo tNewInfo = new IOTSyncObjectInfo(aObject, true);
					ListMap<String, Object>tOldInfos = tOldInfo.getAllInfo();
					ListMap<String, Object>tNewInfos = tNewInfo.getAllInfo();
					
					if (tOldInfos != null && tNewInfos != null){
						for (int i = 0, tCount = tOldInfos.size(); i < tCount; i++){
							String tName = tOldInfos.getKeyAt(i);
							Object tOldValue = tOldInfos.getValueAt(i);
							Object tNewValue = tNewInfos.getValue(tName);
							
							if (tNewValue == null){
								if (tOldValue instanceof IOTSyncObjectInfo){
									
								}else{
									iSynchro.removeInfo(tOldInfo.getOutputName(tName));
								}
							}
						}
					}
				}/////////////////////////
				
				if (tOldInfo != null){
					HashMap<String, String> tOldInfos = tOldInfo.output(true);
					IOTSyncObjectInfo tNewInfo = new IOTSyncObjectInfo(aObject, true);
					HashMap<String, String> tNewInfos = tNewInfo.output(true);
					
					if (tNewInfos != null){
						//Remove the remain info
						if (tOldInfos != null){
							int i, tCount;
							Iterator<Entry<String, String>> tIterator = tOldInfos.entrySet().iterator();
							
							Debugger.Warning(tOldInfos);
							Debugger.Error(tNewInfos);
							while(tIterator.hasNext()){
								Entry<String, String> tEntry = tIterator.next();
								
								if (tEntry != null){
									String tName = tEntry.getKey();
									String tValue = tEntry.getValue();
									
									if (tNewInfos.containsKey(tName) == false){
										synchronized(iSyncLock){
											for (i = 0, tCount = iInfos.size(); i < tCount; i++){
												IOTSyncObjectInfo tInfo = iInfos.getValueAt(i);
												
												if (tInfo != null && tInfo != tOldInfo){
													HashMap<String, String> tInfos = tInfo.output(false);
													
													if (tInfos != null && tInfos.containsKey(tName) && tInfos.containsValue(tValue)){
														break;
													}
												}
											}
										}
										
										if (i == tCount){
											iSynchro.removeInfo(tName);
										}
									}
								}
							}
						}
						
						return save(aObject.getSyncID(), tNewInfo, tNewInfos);
					}
				}
			}
		}
		
		return false;
	}
	
	public boolean add(IOTAbsSyncObject aObject){
		if (aObject != null){
			boolean tRet = false;
			Object tObjects[];
			
			if (iObjects == null){
				iObjects = new ListMap<String, IOTAbsSyncObject>();
			}else{
				synchronized(iSyncLock){
					if (iObjects.containsKey(aObject.getSyncID())){
						return true;
					}
				}
			}
			
			synchronized(iSyncLock){
				if (iObjects.add(aObject.getSyncID(), aObject) == false){
					return false;
				}
			}
			
			tObjects = aObject.getSyncObjects();
			if (tObjects != null){
				for (int i = 0; i < tObjects.length; i++){
					if (tObjects[i] != null && tObjects[i] instanceof IOTAbsSyncObject){
						if (add((IOTAbsSyncObject) tObjects[i]) == false){
							//Remove all added before
							for (int j = i; j >= 0; j--){
								if (tObjects[j] != null && tObjects[j] instanceof IOTAbsSyncObject){
									remove((IOTAbsSyncObject) tObjects[j]);
								}
							}
							
							return false;
						}
					}
				}
			}
			
			if (tRet = save(aObject, true)){
				aObject.setSyncCollector(this);
			}
			
			//Remove all sub item added
			if (tRet == false && tObjects != null){
				for (int i = 0; i < tObjects.length; i++){
					if (tObjects[i] != null && tObjects[i] instanceof IOTAbsSyncObject){
						remove((IOTAbsSyncObject) tObjects[i]);
					}
				}
			}
			
			return tRet;
		}
		
		return false;
	}
	
	private boolean removeFromSynchro(IOTSyncObjectInfo aInfo){
		if (aInfo != null){
			HashMap<String, String> tInfos = aInfo.output(false);
			
			if (tInfos != null){
				Iterator<Entry<String, String>> tIterator = tInfos.entrySet().iterator();
				
				while(tIterator.hasNext()){
					Entry<String, String> tEntry = tIterator.next();
					
					if (tEntry != null){
						iSynchro.removeInfo(tEntry.getKey());
					}
				}
				
				return true;
			}
		}
		
		return false;
	}
	
	private Object findRelativeObject(Object aObject){
		if (aObject != null && iObjects != null){
			synchronized(iSyncLock){
				for (int i = 0, tCount = iObjects.size(); i < tCount; i++){
					Object tObject = iObjects.getValueAt(i);
					
					if (tObject != null && tObject != aObject && tObject instanceof IOTAbsSyncObject){
						Object tObjects[] = ((IOTAbsSyncObject)tObject).getSyncObjects();
						
						if (tObjects != null){
							for (int j = 0; j < tObjects.length; j++){
								if (tObjects[j] == aObject){
									return tObject;
								}
							}
						}
					}
				}
			}
		}
		
		return null;
	}
	
	public boolean remove(IOTAbsSyncObject aObject){
		if (aObject != null && iObjects != null){
			HashMap<String, Object> tInfos;
			
			synchronized(iSyncLock){
				if (iObjects.removeAll(aObject) == 0){
					return false;
				}
			}
			
			if ((tInfos = aObject.getSyncInfos()) != null){
				if (removeFromSynchro(new IOTSyncObjectInfo(aObject.getSyncInfoPrefix(), aObject.getSyncID(), tInfos, false))){
					Iterator<Entry<String, Object>> tIterator = tInfos.entrySet().iterator();
					
					while(tIterator.hasNext()){
						Entry<String, Object> tEntry = tIterator.next();
						
						if (tEntry != null){
							Object tValue = tEntry.getValue();
							
							if (tValue != null && tValue instanceof IOTAbsSyncObject){
								if (findRelativeObject(tValue) == null){
									remove((IOTAbsSyncObject) tValue);
								}
							}
						}
					}
				}
				
				return true;					
			}
		}
		
		return false;
	}
	
}
